# $Id: _util.py 846 2013-06-16 21:37:50Z onealj $

from __future__ import division

from com.loral.ssd.staar.corba.AuthenticatedUserRepositoryPackage import StateEvent2, SystemException
from javax.swing import JFrame, Box, BoxLayout, JTabbedPane, JPanel,  JOptionPane, JCheckBox, JLabel, JTextField, JRadioButton, ButtonGroup, JMenuItem, JMenu, BorderFactory, JComboBox
from org.jfree.data.xy import YIntervalSeriesCollection
from xml.etree import ElementTree

import urllib2 #for opening web files
from urllib2 import HTTPError
import math, operator #operator needed for try_math functions
import bisect
import functools #needed for memoized class

import _ssi
from _backport import OrderedDict
import _chart
import _names

NaN = float('nan')
NEG_INF = float('-inf')
POS_INF = float('inf')

class memoized(object):
  '''Decorator that caches a function's return value each time it is called.
  If called later with the same arguments, the cached value is returned, and
  not re-evaluated.
  
  CODE SOURCE: http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
  Code is duplicated in _util and _SpecMaskClass
  '''
  def __init__(self, func):
    self.func = func
    self.cache = {}
  def __call__(self, *args):
    try:
      return self.cache[args]
    except KeyError:
      value = self.func(*args)
      self.cache[args] = value
      return value
    except TypeError:
      # uncachable -- for instance, passing a list as an argument.
      # Better to not cache than to blow up entirely.
      return self.func(*args)
  def __repr__(self):
    """Return the function's docstring."""
    return self.func.__doc__
  def __get__(self, obj, objtype):
    """Support instance methods."""
    return functools.partial(self.__call__, obj)
    
def get_highlighted_record(app):
  record_zero = app.payloadFileRecordTable.highlightedPayloadFileRecord
  if record_zero == None:
    try:
      record_zero = app.payloadFileRecordTable.selectedRecords[0]
    except IndexError, e:
      error_dialog('At least one record must be selected')
  return record_zero
  
def get_staar_data(app, record_id):
  test_result_data = app.staarAuthenticatedUserRepository.getTestResultData(record_id)
  data_gdf = GdfWrapper(test_result_data[0].data) #test rack data
  data_ppd = GdfWrapper(test_result_data[1].data) #post-processed data
  return data_gdf, data_ppd
    
      
def error_dialog(message, title='Error'):
  JOptionPane.showMessageDialog(None, message, title, JOptionPane.ERROR_MESSAGE)
def warning_dialog(message, title='Warning'):
  JOptionPane.showMessageDialog(None, message, title, JOptionPane.WARNING_MESSAGE)
def info_dialog(message, title='Information'):
  JOptionPane.showMessageDialog(None, message, title, JOptionPane.INFORMATION_MESSAGE)

istc_payload_dir = 'http://k2.ssd.loral.com/svn/istc/Payload'
urlsep = '/'

def svn_dir_dict():
  import urllib2
  import csv
  try:
    f = urllib2.urlopen(urlsep.join([istc_payload_dir, 'zzz_dut_to_directory_map.tsv']))
  except HTTPError, e:
    svn_dir = istc_payload_dir
    svn_dir = JOptionPane.showInputDialog(None,
                                          'Error! \n' +
                                          'ISTC svn directory was not found\n' + 
                                          'Please specify the url of the spacecraft ISTC directory', svn_dir)
    if svn_dir:
      f = urllib2.urlopen(svn_dir)
    else:
      raise Exception('User pressed cancel when specifying the ISTC directory. Script execution was aborted')
    
  d = dict(csv.reader(filter(lambda row: row[0] != '#', f), csv.excel_tab))
  return d

@memoized #caches algorithm output
def get_svn_dir(spacecraft_name):
  #print urlsep.join([istc_payload_dir, svn_dir_dict()[spacecraft_name]])
  return urlsep.join([istc_payload_dir, svn_dir_dict()[spacecraft_name]])

def get_param_file_handle(spacecraft_name, param_file_name):
  svn_dir = get_svn_dir(spacecraft_name)
  url = urlsep.join([svn_dir, 'parameter', param_file_name])
  try:
    web_file = urllib2.urlopen(url)
  except HTTPError, e:
    url = JOptionPane.showInputDialog(None, 'Specify the URL of the ' + param_file_name + ' parameter file', url)
    try:
      web_file = urllib2.urlopen(url)
    except Exception, e:
      JOptionPane.showMessageDialog(None,
                                    'Error! \n ' + param_file_name + ' parameter file was not found at ' + url,
                                    'STAAR+: Error', JOptionPane.ERROR_MESSAGE)
      raise e
  return web_file

def get_pdf_url(app, record):
  pdf_object = app.staarAuthenticatedUserRepository.getPdfs([record.id])[0]
  pdf_filename = pdf_object.filename
  
  data_gdf = GdfWrapper(app.staarAuthenticatedUserRepository.getParsedGdf(record.id))
  model, flight = data_gdf['SSL_Model'], data_gdf['SSL_Serial']
  pdf_url = 'http://staar/staar/%s_%s/COMM/reports/%s' % (model.upper(), flight.upper(), pdf_filename)
  return pdf_url

def get_test_types_by_spacecraft_name(app, spacecraft):
  '''Output: test_types is a list of test type tuples for the selected spacecraft
  test_types = [(1010, u'Gain Steps: FGM', u''),
                (1011, u'Gain Steps: ALC', u''),
                (1060, u'Frequency Response: Linear', u''), ...]
  '''
  test_types = [(tt.id, tt.name) for tt in app.staarAuthenticatedUserRepository.getTestTypesBySpacecraftName(spacecraft)]
  #tt.testTypeFamily is no longer used
  return test_types
  
def get_test_phases_by_spacecraft_name(app, spacecraft):
  '''Output: test_phases is a list of test phase tuples for the selected spacecraft.
  Each tuple contains a test phase number and description.
  test_types = [(u'10', u'Panel RF Test'),
                (u'12', u'End To End SATing'),
                (u'13', u'Ref Performance'), ...]
  '''
  test_phases = [(tp.identifier, tp.name) for tp in app.staarAuthenticatedUserRepository.getTestPhasesBySpacecraftName(spacecraft)]
  return test_phases
  

@memoized
def get_user_attributes(app, spacecraft, subsystem='RPTR'):
  try:
    xml_text = ''.join(map(chr, app.staarAuthenticatedUserRepository.getUserAttributesDefinitionDocumentBySpacecraftName(spacecraft)))
    root = ElementTree.fromstring(xml_text)
    user_attributes = OrderedDict()
    for payload_subsystem in root:
      if payload_subsystem.attrib['name'] == subsystem:
        for ssi_field in payload_subsystem:
          user_attributes[ssi_field.attrib['code']] = ssi_field.attrib
          #attrib = {'code': 'LNA/RCVR', 'length': '2', 'position': '0', 'description': 'LNA/RCVR', 'code2': 'LNA/RCVR'}
    return user_attributes
  except SystemException, e:
    return {}
        
def get_user_attribute_field_value(redundancy_string, user_attributes, code):
  '''returns the user attribute corresponding to chosen code'''
  startIdx = int(user_attributes[code]['position'])
  length = int(user_attributes[code]['length'])
  stopIdx = startIdx + length
  return redundancy_string[startIdx:stopIdx]
  

def getChosenSSIFieldCodes(app, spacecraft, subsystem, full_ssi):
  ssiFieldCodes = [code for code in _ssi.get_field_codes(app, spacecraft, subsystem, full_ssi) if ':' not in code] #trim out ':' from SSI
  
  try:
    field_descriptions = _ssi.get_field_descs(spacecraft, subsystem)
    try:
      ssiFieldCodes = ['%s (%s)' % (code, field_descriptions[code]) for code in ssiFieldCodes] #add field description
    except:
      new_field_descriptions = field_descriptions#added for FPS. Added by S. Vannala
      new_dict = {'UL': 'UL','DL': 'DL','R': 'R', 'P':'P','CH':'CH','AT':'AT'}#added for FPS. Added by S. Vannala
      new_field_descriptions.update(new_dict)#added for FPS. Added by S.Vannala
      ssiFieldCodes = ['%s (%s)' % (code, new_field_descriptions[code]) for code in ssiFieldCodes] #add field description. Added S. Vannala to trend FPS stability data
  except HTTPError, e:
    print(str(e) + ': Unable to open SSI Definition file. SSI Field descriptions not added to SSI Field Codes menu')
  # except KeyError, e:
    # print(str(e) + ': key not found in field_descriptions dictionary')
    # pass
  
  title = 'Split plots by field'
  message = 'Choose which SSI fields to plot in separate tabs'
  
  #redundancyFieldCodes = ['LNA/RCVR', 'DC/UC', 'LTWTA']
  
  redundancyFieldCodes = get_user_attributes(app, spacecraft, subsystem)
  otherFields = ['SSI', 'Redundancy', 'Config', 'Test Phase', 'Test Type', 'Run Number', 'In Spec', 'SS/L Workflow', 'Customer Workflow', 'Year (Local)', 'Month (Local)', 'Day (Local)', 'Hour (Local)', 'Block/Sequence'] #'Latest Run'
  checked_fields = prompt_split_by_field(ssiFieldCodes, redundancyFieldCodes, otherFields)
  return checked_fields
    
def getChosenSSIFieldValues(app, record, data_ppd, selected_field_codes):
  '''
  returns a list of strings, corresponding one-for-one with selected_field_codes
  '''
  ssiFieldCodes, redFieldCodes, otherFieldCodes = selected_field_codes
  ssi_wrapper = _ssi.break_full_ssi_into_fields(app, record.spacecraftName, record.payloadSubsystem, record.ssi)
  ssi_field_values = _ssi.get_selected_field_values(ssi_wrapper, ssiFieldCodes)
  user_attributes = get_user_attributes(app, record.spacecraftName, record.payloadSubsystem)
  redundancy_field_values = [get_user_attribute_field_value(record.ssi2, user_attributes, code) for code in redFieldCodes]
  other_field_values = [get_other_field_value(record, data_ppd, code) for code in otherFieldCodes]
  #bool(list) returns True if list is non-empty or non-none
  dash1 = '-' * (bool(ssi_field_values) and (bool(redundancy_field_values) or bool(other_field_values)))
  dash2 = '-' * (bool(redundancy_field_values) and bool(other_field_values))
  del ssi_wrapper
  return [item for sublist in [ssi_field_values, dash1, redundancy_field_values, dash2, other_field_values] for item in sublist]
  
def get_other_field_value(record, data_ppd, code):
  if code == 'SSI':
    return record.ssi
  if code == 'Config':
    return record.config
  elif code == 'Test Phase':
    return record.testPhase
  elif code == 'Test Type':
    return str(record.testType)
  elif code == 'Run Number':
    return record.runNumber
  #elif code == 'Latest Run':
  #  return '' #XML query?
  elif code == 'In Spec':
    try:
      spec_package_counts = data_ppd.values(key_startswith='Spec_Pkg_', key_endswith='_Count')
      if len(spec_package_counts) == 0:
        print('Unable to find Spec Package Count')
        return ''
      elif sum(spec_package_counts) == 0:
        return 'InSpec'
      else:
        return 'OOS'
    except:
      print('Spec_Pkg_#_Count not found within data_ppd')
      return ''
  elif code == 'SS/L Workflow':
    return record.stateSSL
  elif code == 'Customer Workflow':
    return record.stateCustomer
  elif code == 'Date/Time (Local)':
    return record.testStartTime
  elif code == 'Date (Local)':
    return record.testStartTime.split(' ',1)[0] #u'2012-11-08 04:51:42-08'
  elif code == 'Year (Local)':
    return record.testStartTime.split(' ',1)[0].split('-',2)[0]
  elif code == 'Month (Local)':
    return record.testStartTime.split(' ',1)[0].split('-',2)[1]
  elif code == 'Day (Local)':
    return record.testStartTime.split(' ',1)[0].split('-',2)[2]
  elif code == 'Time (Local)':
    return record.testStartTime.split(' ',1)[1][:-3] #u'2012-11-08 04:51:42-08'
  elif code == 'Hour (Local)':
    return record.testStartTime.split(' ',1)[1].split(':',2)[0]
  elif code == 'Minute (Local)':
    return record.testStartTime.split(' ',1)[1].split(':',2)[1]
  elif code == 'Second (Local)':
    return record.testStartTime.split(' ',1)[1].split(':',2)[1] #u'2012-11-08 04:51:42-08'
  elif code == 'Timezone':
    return record.testStartTime.split(' ',1)[1][-3:] #u'2012-11-08 04:51:42-08'
    #alternatively, parse using datetime.strptime(date_string, format)
    #see http://docs.python.org/2/library/datetime.html#datetime-objects
  elif code == 'Block/Sequence':
    return record.blockSeq
  elif code == 'User Attr' or code == 'Redundancy':
    return record.ssi2
  else:
    print(code+' code not found')
    return ''
  
def collapseStrList(strlist, joinStr, stringIfEmpty):
  if strlist == None or len(strlist) == 0 or strlist[0] == None:
    return stringIfEmpty
  else:
    return joinStr.join(strlist)
  
def dBm_sum(x_dBm, y_dBm):
  # adds two dBm numbers in linear (i.e. Watts) and returns the sum (in dBm)
  x_Watts = [10**((i-30)/10) for i in x_dBm]
  y_Watts = [10**((i-30)/10) for i in y_dBm]

  z_dBm = [10*math.log10(i+j) + 30 for i,j in zip(x_Watts, y_Watts)] 
 
  return z_dBm

def distribute(str, str_list):
  '''
  Concatenates a single string with each string in str_list
  Returns a list of strings
  Note: works on any data type supporting the __add__ method (including numerics)
  >>> distribute('lan', ['yard', 'd ho!', 'gley'], 'fish')
  ['lanyard', 'land ho!', 'langley']
  '''
  return [str + i for i in str_list]
def rightdistribute(str_list, str):
  '''
  Concatenates a single string with each string in str_list
  Returns a list of strings
  Note: works on any data type supporting the __add__ method (including numerics)
  >>> rightdistribute(['dog', 'cat', 'tuna '], 'fish')
  ['dogfish', 'catfish', 'tuna fish']
  '''
  return [i + str for i in str_list]

def invert_dictionary(adict):
  '''Input: adict = {(key,value)}
  Output: {(value, [list of keys])}
  Returns a dictionary containing (value, [list of keys associated with value]) pairs.
  Since multiple keys may reference the same value in the noninverted dictionary,
  invert_dictionary returns a list of keys associated with each value.'''
  inv_dict = adict.__class__() #create an empty dictionary of the same type of adict (which could be a dictionary-like object like OrderedDict)
  for k, v in adict.iteritems():
    if v not in inv_dict:
      inv_dict[v] = []
    inv_dict[v].append(k)
  return inv_dict
  
def diff(ylist,xlist=None):
  '''Precondition: a is iterable and supports the subtraction method on its elements
  If xlist is not None, calculates slope. See also _util.calcSlope'''
  if xlist == None:
    return [ylist[i+1] - ylist[i] for i in range(0, len(ylist)-1)]
  else:
    return [diff(y) / diff(x) for x,y in zip(xlist, ylist)]

def try_abs(a):
  '''try-except wrapper for abs(a), returns input if an error occurs'''
  try:
    return abs(a)
  except TypeError, e:
    return a
    
def try_mul(a,b,default=NaN):
  '''try-except wrapper for operator.mul(a,b), returns input if an error occurs'''
  try:
    return operator.mul(a,b)
  except TypeError, e:
    return default
    
def try_div(a,b,default=NaN):
  '''try-except wrapper for operator.truediv(a,b), returns input if an error occurs'''
  try:
    return operator.truediv(a,b)
  except (TypeError, ZeroDivisionError), e:
    return default

def try_add(a,b, default=NaN):
  '''try-except wrapper for operator.sum(a,b), returns input if an error occurs'''
  try:
    return operator.add(a,b)
  except TypeError, e:
    return default
    
def try_sub(a,b, default=NaN):
  '''try-except wrapper for operator.sub(a,b), returns input if an error occurs'''
  try:
    return operator.sub(a,b)
  except TypeError, e:
    return default

def dot(a,b):
  '''returns the inner sum (dot product) of two vectors a and b'''
  vec = map(try_mul,a,b,[NaN]*len(a)) #element-wise multiplication of a and b
  return reduce(try_add,vec) #sum of elements in vec
  
def weighted_average(x, weight):
  x_clean = filter(isfinite, x)
  return sum(dot(x, weight)) / sum(weight)

def average(x):
  x_clean = filter(isfinite, x)
  return sum(x_clean)/len(x_clean)

def stddev(x):
  mean = average(x)
  s = (1/len(x) * sum([(xi-mean)**2 for xi in x]))**0.5
  return s

def isnumber(var):
  return isinstance(var, (int, long, float, complex))

def isnan(number):
  return number != number

def isfinite(number):
  return (isnumber(number) and
          not isnan(number) and
          number not in [NEG_INF, POS_INF])
  

def smooth(x, smoothPoints=3):
  w = smoothPoints//2
  return [NaN]*w + [average(x[i-w:i+w+1]) for i in range(w, len(x)-w)] + [NaN]*w 
  
def calcSlope(x, y, smoothPoints=3, absoluteValue=True, growthFactor=1.4):
  '''calculates the slope of a line with points x and y
  
  >>> calcSlope([2,3,4,5,6,7,8,9,10],[22, 23, 24, 25, 26, 25.5, 24.5, 23.5, 22.5],smoothPoints=2, absoluteValue=False, growthFactor=1.0)
  [nan, 1.0, 1.0, 1.0, 0.25, 0.75, 1.0, 1.0, nan]
  '''
  w = smoothPoints//2
  growthWeight = [growthFactor**i for i in range(w+1)] #1.4 is a good number for exponential decay
  decayWeight = list(reversed(growthWeight))
  slope = [NaN]*w + \
          [try_div(dot(y[i:i+w+1],decayWeight) - dot(y[i-w:i+1],growthWeight),
                   dot(x[i:i+w+1],decayWeight) - dot(x[i-w:i+1],growthWeight)) for i in range(w,len(x)-w)] + \
          [NaN]*w
  
  if absoluteValue:
    slope = map(try_abs,slope)
      
  return slope
  
def interpolate(x, y, x_eval, extrap=False, method='linear'):
  '''x_eval can either be a single point or a sequence of points
  interpolate returns a single point or sequence of points over which x and y are defined
  
  >>> interpolate([1,5,9],[4, 500, 30],range(0,10),extrap=False,method='nearest')
  [4, 4, 4, 500, 500, 500, 500, 30, 30, 30]
  '''
  assert len(x) == len(y), 'x and y must be of equal lengths'
  
  try:
    #test if x_eval is numeric value, not a list
    float(x_eval)
    assert type(x_eval) != str, 'x_eval must be numeric'
  except TypeError, e:
    #x_eval is a list
    return [interpolate(x, y, xi, extrap, method) for xi in x_eval]

  if len(x) == 1:
    return y[0]
    
  #sort x and y in ascending order of x
  x, y = zip(*sorted(zip(x,y)))
  
  #check if x_eval is out of bounds (extrapolate or throw an error)
  #TO DO: use the .bisect_left or .bisect_right method of a list (after importing bisect) to find proper location in list.
  if x[0] <= x_eval < x[-1]:
    i = bisect.bisect_right(x, x_eval) - 1
  else:
    if extrap:
      if x_eval < x[0]:
        i = 0
      else:
        i = len(x)-2
    else:
      raise ValueError('x_eval (%0.2f) must be between xmin (%0.2f) and xmax (%0.2f)' % (float(x_eval), float(x[0]), float(x[-1])))
  
  
  #find nearest x0 <= x_eval <= x1 at index i, with i corresponding to lower, i+1 corresponding to upper
  x0 = x[i]
  x1 = x[i+1]
  y0 = y[i]
  y1 = y[i+1]

  if method == 'linear':
    return float(x_eval - x0) * (y1 - y0) / (x1 - x0) + y0
  elif method == 'nearest':
    if abs(x0 - x_eval) < abs(x1 - x_eval):
      return y0
    else:
      return y1
    #return numpy.where(numpy.abs(xout - x0) < numpy.abs(xout - x1), y0, y1)
  else:
    raise ValueError('Invalid interpolation method: %s' % method)# Python code here


def unique(seq, idfun=None):
  '''returns a sorted list of the unique items in seq
  alist is any iterable
  This method preserves order
  See http://www.peterbe.com/plog/uniqifiers-benchmark for additional implementations
  ''' 
  # order preserving
  if idfun is None:
    def idfun(x): return x
  seen = {}
  result = []
  for item in seq:
    marker = idfun(item)
    # in old Python versions:
    # if seen.has_key(marker)
    # but in new ones:
    if marker in seen:
      continue
    seen[marker] = 1
    result.append(item)
  return result
    

def setTestResultDict(tableRef, *innerTabs):
  '''adds tableRef tab to testResults and adds innerTabs sub-tabs in tableRef tab'''
  testResultsDict = {}
  testResultsDict[tableRef] = {}
  for tab in innerTabs:
    testResultsDict[tableRef][tab]=[]

  return testResultsDict
  
def swapInnerAndOuterTabs(test_results):
  #in order for this function to be used, tabbed_plot_specs needs to be modified such that the old outer tab title becomes the plot title
  swapped_results = {}
  for outer in test_results:
    for inner in test_results[outer]:
      if inner not in swapped_results:
        swapped_results[inner] = {}
      swapped_results[inner][outer] = test_results[outer][inner]
  return swapped_results

# The function get_ssi_list(programName, testType) was removed at r574 because
# it is no longer used in other STAAR+ Python scripts.
# The function was moved and rewritten in _SpecMaskClass.py many versions ago.

  
def get_tableRef(ssiStr):
  """
	PURPOSE:	Function is to find the tableRef of the ssi being tested.

	PRE:		ssi = SSI string from Staar.

	POST:		Returns test type(tableRef) of the ssi belongs to.

  """
  error_dialog('get_tableRef function moved to specMaskClass.py')
  '''
  ssiStr = str(ssiStr) #Convert ssi to string
  tableRef = "" 
  for k, v in ssiDict.iteritems(): #Iterate throught Global variable ssiDict
    for ssi in v:
      ssi_re = re.compile(ssi)
      if ssi_re.match(ssiStr):
        return k
  '''

def get_SpecMasks(axis):
  error_dialog('get_SpecMasks function moved to specMaskClass.py')
  '''
  pointList = []
  specMaskDict = {}
  for tables in tableRefList:
    for child in tables.getchildren():
      for point in child.getiterator('point'):
        pointList.append(float(point.attrib[axis]))
      if tables.attrib['id'] not in specMaskDict:
        specMaskDict[tables.attrib['id']] = []
      specMaskDict[tables.attrib['id']].append(pointList)
      pointList = []
  return specMaskDict
  '''

class GdfWrapper(object):
  def __init__(self, data):
    ######## Use the following to move the data from a O(n) to a O(1) lookup data structure
    ## cost of converting from special data structure to a dictionary is
    ## more expensive than the few __getitem__ and keys() calls to GdfWrapper
    # self.dict = OrderedDict()
    # for field in data:
    #  # self.dict[field.name] = field.value
    ##########
    self.data = data
  
  def __len__(self):
    return len(self.data)
    
  def __eq__(self, other):
    '''order of keys/items matters for checking equality'''
    if type(other) is type(self) and len(self) == len(other):
      for selfitem, otheritem in zip(self.iteritems(), other.iteritems()):
        if selfitem != otheritem:
          return False
      return True
    else:
      return False
      
  def __ne__(self, other):
    return not self.__eq__(other)
  
  def __iter__(self):
    return self.iterkeys()

  def __contains__(self, name):
    #Test membership "key in data_gdf" is equivalent to data_gdf.__contains__(key)
    for i in self.data:
      if i.name == name:
        return True
    return False

  def __getitem__(self, name):
    #linearly searches through name list.
    #No need to optimize: approx 0.5% of program execution time is due to getitem
    ######## Use the following to move the data from a O(n) to a O(1) lookup data structure
    #v = self.dict[name]
    ####################
    
    for i in self.data:
      if i.name == name:
        if hasattr(i, 'data'): #traverse one level down data.data
          return GdfWrapper(i.data)
        v = i.value
        break
    else:
      raise KeyError('no such key: ' + str(name))
    
    
  
    d = v.discriminator()
    if d.value() == d._STRING_VAL: # 0
      return v.m_string()
    elif d.value() == d._STRINGSEQ_VAL: # 1
      return v.m_StringSeq()
    elif d.value() == d._LONG_VAL: # 2
      return v.m_long()
    elif d.value() == d._LONGSEQ_VAL: # 3
      return v.m_LongSeq()
    elif d.value() == d._DOUBLE_VAL: # 4
      return v.m_double()
    elif d.value() == d._DOUBLESEQ_VAL: # 5
      return v.m_DoubleSeq()
    else:
      raise AssertionError('unknown discriminator value'+str(d.value()))
      
  def try_getitem(self, list_of_names):
    '''try_getitem(list_of_names) returns the value corresponding to the first matching name (key) in list_of_names'''
    for name in list_of_names:
      try:
        return self.__getitem__(name)
      except KeyError, e:
        continue
    else:
      raise KeyError('unable to find a matching key from ' +str(list_of_names) + ' in GDF')
      
  def try_getitem_partial(self, list_of_partial_names):
    '''returns the first GDF value with a partial match between the GDF's key and a name in list_of_partial_names'''
    for partial_name in list_of_partial_names:
      for gdf_key in self.iterkeys():
        if partial_name in gdf_key:
          return self.__getitem__(gdf_key)
    else:
      raise KeyError('unable to find a matching key from ' +str(list_of_partial_names) + ' in GDF')
    
  def get(self, name, default=None):
    '''
    GDF.get(k[,d]) -> GDF[k] if k in GDF, else d.  d defaults to None
    '''
    if name in self:
      return self.__getitem__(name)
    else:
      return default
      
  def keys(self, key_contains='', key_startswith='', key_endswith=''):
    '''returns a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string
    '''
    return list(self.iterkeys(key_contains, key_startswith, key_endswith))
  def iterkeys(self, key_contains = '', key_startswith='', key_endswith=''):
    '''returns a generator to a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string
    '''
    for field in self.data:
      if (key_contains in field.name and
          field.name.startswith(key_startswith) and
          field.name.endswith(key_endswith)):
        yield field.name
  
  def values(self, key_contains = '', key_startswith='', key_endswith=''):
    '''returns a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string
    '''
    return list(self.itervalues(key_contains, key_startswith, key_endswith))
  def itervalues(self, key_contains = '', key_startswith='', key_endswith=''):
    '''returns a generator to a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string
    '''
    for field in self.data:
      if (key_contains in field.name and
          field.name.startswith(key_startswith) and
          field.name.endswith(key_endswith)):
        yield self.__getitem__(field.name)
  
  def items(self, key_contains = '', key_startswith='', key_endswith=''):
    '''returns a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string
    '''
    return list(self.iteritems(key_contains, key_startswith, key_endswith))
  def iteritems(self, key_contains = '', key_startswith='', key_endswith=''):
    '''returns a generator to a list of the GDF dictionary keys.
    if key_contains is specified, method returns only items where key_contains is a substring of key
    key_startswith and key_endswith are similar, but compare start or end of string'''
    for field in self.data:
      if (key_contains in field.name and
          field.name.startswith(key_startswith) and
          field.name.endswith(key_endswith)):
        yield (field.name, self.__getitem__(field.name))
    
  def datatype(self, name):
    '''returns datatype corresponding to name
    convert to string and compare with:
    ('STRING_VAL', 'STRINGSEQ_VAL', 'LONG_VAL', 'LONGSEQ_VAL', 'DOUBLE_VAL', 'DOUBLESEQ_VAL')
    '''
    for data in self.data:
      if data.name == name:
        return data.value.discriminator()
        
  def datatypes(self):
    return list(self.iterdatatypes())
    
  def iterdatatypes(self):
    return (d.value.discriminator() for d in self.data)
  
  def is_sequence(self, name):
    return 'SEQ' in self.datatype(name)
    
  def as_ordered_dict(self):
    return OrderedDict(self.iteritems())


def setLinesVisible(chart, isvisible):
  print('WARNING: _util.setLinesVisible function has been moved to _chart. Please update your code accordingly')
  return _chart.setLinesVisible(chart, isvisible)
      
def setShapesVisible(chart, isvisible):
  print('WARNING: _util.setShapesVisible function has been moved to _chart. Please update your code accordingly')
  return _chart.setShapesVisible(chart, isvisible)

def create_chart(title, x_label, y_label, data_set, data_set2=YIntervalSeriesCollection()):
  print('WARNING: _util.create_chart function has been moved to _chart. Please update your code accordingly')
  return _chart.create_chart(title, x_label, y_label, data_set, data_set2)

def getSeriesCount(chart):
  print('WARNING: _util.getSeriesCount function has been moved to _chart. Please update your code accordingly')
  return _chart.getSeriesCount(chart)

def plot(title, x_label, y_label, data):
  print('WARNING: _util.plot function has been moved to _chart. Please update your code accordingly')
  return _chart.plot(title, x_label, y_label, data)

def box_layout_plot(frame_title, plot_info, layout_axis=BoxLayout.X_AXIS):
  print('WARNING: _util.box_layout_plot function has been moved to _chart. Please update your code accordingly')
  return _chart.box_layout_plot(frame_title, plot_info, layout_axis)

def tabbed_plot(frame_title, plot_info):
  print('WARNING: _util.tabbed_plot function has been moved to _chart. Please update your code accordingly')
  return _chart.tabbed_plot(frame_title, plot_info)

def tabbed_plot_dual_axis_specs(frame_title, plot_data_left, plot_data_right, plot_info):
  print('WARNING: _util.tabbed_plot_dual_axis_specs function has been moved to _chart. Please update your code accordingly')
  return _chart.tabbed_plot_dual_axis_specs(frame_title, plot_data_left, plot_data_right, plot_info)

def tabbed_plot_specs(frame_title, plot_data, plot_info):
  print('WARNING: _util.tabbed_plot_specs function has been moved to _chart. Please update your code accordingly')
  return _chart.tabbed_plot_specs(frame_title, plot_data, plot_info)

def set_default_chart_display(chart):
  print('WARNING: _util.set_default_chart_display function has been moved to _chart. Please update your code accordingly')
  _chart.set_default_chart_display(chart)

def getSpecCornerTicksFromPPD(data_ppd):
  """
  PURPOSE:  Function is to get the spec corner and ticks plots from PPD file. Spec_p0_x0_min
  PRE:  data_ppd is an instance.  It contains data from PPD file.
  POST:  Returns spec corner and ticks in Dict.

  """
  graphPlots = {}
  graphPlots['plot1'] = []
  graphPlots['plot2'] = []
  graphPlots['plot3'] = []
        
  for key in data_ppd.iterkeys():
    if 'Spec_p1' in key:
      #key.name = [Spec_p1_x0_min, Spec_p1_x0_max, Spec_p1_x0_major, Spec_p1_x0_minor, 
      #            Spec_p1_y0_min, Spec_p1_y0_max, Spec_p1_y0_major, Spec_p1_y0_minor]
      graphPlots['plot1'].append(data_ppd[key])
    elif 'Spec_p2' in key:
      graphPlots['plot2'].append(data_ppd[key])
    elif 'Spec_p3' in key:
      graphPlots['plot3'].append(data_ppd[key])
    else:
      pass
  
  return graphPlots
  
def get_spec_name_for_record(specMaskObj, testType, record, data_ppd, tabTitleList):
  '''Gets the spec table name for record (tableRefKey)
  side-effect: appends spec name to tabTitleList'''
  graphPlots = getSpecCornerTicksFromPPD(data_ppd)
  tableRefKey = None
  for value in graphPlots.itervalues(): #for each innerTab
    if len(value) > 1:
      tableRefKey = specMaskObj.getTableRef(testType, record.testPhase, record.ssi, value)
      break
  del graphPlots
  if tableRefKey != None:
    if len(tabTitleList) > 0:
      tabTitleList.append(' ')
    tabTitleList.append(tableRefKey)
  else:
    print('Unable to find spec mask for %s. Make sure specs.xml and data_ppd plot limits match.' % record.ssi)
  return tableRefKey
  
def prompt_legend_options():

  legend_choice_list = ['SSI', 'Config ID']
  legend_choice = JOptionPane.showInputDialog(None, 'Please specify what type of legend you would like to see:', 'Selection Screen', 1, 
                                                None, legend_choice_list, legend_choice_list[0])
  return legend_choice

def get_legend_string(legend_choice, app, record, data_gdf, data_ppd={}):
  return get_series_name(record, legend_choice)
  
def get_series_name(record, legend_choice='both'):
    ssi = record.ssi
    config = record.config
    ssi_config = {'both' : '%s %s' % (ssi, config), 'SSL_SSI' : ssi, 'SSI' : ssi, 'SSL_ConfigNum' : config, 'Config ID' : config}[legend_choice]
    redundancy = record.ssi2
    
    test_phase = record.testPhase
    test_type = str(record.testType)
    run_number = str(record.runNumber)
    ssl_state = record.stateSSL

    legend = ' '.join((ssi_config, redundancy, "(P:%s | T:%s | R:%s | S:%s)" % (test_phase, test_type, run_number, ssl_state)))
    return legend


def get_abridged_legend_string(legend_choice, data):
    
    legend_table = { 
      'SSI'        : 'SSL_SSI',
      'Config ID'  : 'SSL_ConfigNum'
    }

    try:
      return data[legend_table[legend_choice]]
    except KeyError, e:
      try:
        return data[legend_choice]
      except KeyError, e:
        return 'Invalid legend_choice'


def prompt_legend_display():

    options = ["No", "Yes"]

    legend_option = JOptionPane.showOptionDialog(None,
       "Would you like to see the legend displayed?",
       "You Like Legend?",
       JOptionPane.YES_NO_OPTION,
       JOptionPane.QUESTION_MESSAGE,
       None,     # do not use a custom Icon
       options,  # the titles of buttons
       options[0]); # default button title

    return legend_option


    
def create_radio_button_group(options, title=None):
  logical_group = ButtonGroup()
  box = Box.createVerticalBox()
  for index, option in enumerate(options):
    radio_button = JRadioButton(option, index==0)
    box.add(radio_button)
    logical_group.add(radio_button)
  if title != None:
    border = BorderFactory.createTitledBorder(title)
    box.setBorder(border)
  return box, logical_group
    

def get_plottingOptions(checkBox, radioButton, title, message, defaultCheckboxState=True):
  '''defaultCheckboxState is a boolean scalar or boolean vector of length checkBox'''
  promptBox = Box(BoxLayout.Y_AXIS)
  userOptions = []
  
  label = JLabel(message)
  promptBox.add(label)
  group = ButtonGroup()
  
  for count, option in enumerate(radioButton):
    radioButton = JRadioButton(option)
    radioButton.setSelected(count == 0)
    group.add(radioButton)
    promptBox.add(radioButton)
    userOptions.append(radioButton)
  
  
  try:
    #defaultCheckboxState is a vector
    if len(defaultCheckboxState) < len(checkBox):
      print('_util.get_plottingOptions: length of defaultCheckboxState must match length of checkBox')
      defaultCheckboxState.extend([True] * (len(checkBox)-len(defaultCheckboxState))) #any unspecified checkbox defaults to True
  except TypeError, e:
    #defaultCheckboxState is a scalar
    defaultCheckboxState = [defaultCheckboxState] * len(checkBox)
    
  for option, state in zip(checkBox, defaultCheckboxState):
    checkBox = JCheckBox(option)
    checkBox.setSelected(state)
    promptBox.add(checkBox)
    userOptions.append(checkBox)
    
  return (JOptionPane.showConfirmDialog(None, promptBox, title, JOptionPane.OK_CANCEL_OPTION),
         [userOption.isSelected() for userOption in userOptions])
    
def get_plottingOptions2(checkBox, radioButton, title, message, defaultCheckboxState=True):
  '''new version of the plotting options routine
  defaultCheckboxState is a boolean scalar or boolean vector of length checkBox'''
  if len(radioButton) == 0 or isinstance(radioButton[0], basestring):
    radioButton = [radioButton]
  
  promptBox = Box(BoxLayout.Y_AXIS)
  userOptions = []
  
  label = JLabel(message)
  promptBox.add(label)
  logical_button_groups = []
  
  for group_index, radio_group in enumerate(radioButton):
    logical_button_groups.append(ButtonGroup())
    radio_button_group = []
    promptBox.add(create_radio_button_group(radio_group, 'Group')[0])
    # for option_index, radio_option in enumerate(radio_group):
      # radioButton = JRadioButton(radio_option, option_index == 0)
      # logical_button_groups[group_index].add(radioButton)
      # promptBox.add(radioButton) #or move outside and add radio_button_group
      # radio_button_group.append(radioButton)
      # userOptions.append(radioButton)
    # # userOptions.append(radio_button_group)
  
  
  try:
    #defaultCheckboxState is a vector
    if len(defaultCheckboxState) < len(checkBox):
      print('_util.get_plottingOptions: length of defaultCheckboxState must match length of checkBox')
      defaultCheckboxState.extend([True] * (len(checkBox)-len(defaultCheckboxState))) #any unspecified checkbox defaults to True
  except TypeError, e:
    #defaultCheckboxState is a scalar
    defaultCheckboxState = [defaultCheckboxState] * len(checkBox)
    
  for option, state in zip(checkBox, defaultCheckboxState):
    checkBox = JCheckBox(option)
    checkBox.setSelected(state)
    promptBox.add(checkBox)
    userOptions.append(checkBox)
    
  return (JOptionPane.showConfirmDialog(None, promptBox, title, JOptionPane.OK_CANCEL_OPTION),
         [userOption.isSelected() for userOption in userOptions])

def prompt_with_checkbox_options(title, message, options):
# Example Input: prompt_with_options('Title', 
#   'Please select between the following options', ['Item1', 'Item2'])
# Example Output: (0, [True, False])

  box = Box(BoxLayout.Y_AXIS)
  checkBoxes = []

  label = JLabel(message)
  box.add(label)
  
  for option in options:
    checkBox = JCheckBox(option)
    box.add(checkBox)
    checkBoxes.append(checkBox)

  return (JOptionPane.showConfirmDialog(None, box, title, JOptionPane.OK_CANCEL_OPTION),
         [checkBox.isSelected() for checkBox in checkBoxes])

def prompt_reference(default_test_type, default_test_phase='13', app=None, spacecraft_name=None):
  '''creates a dialog for the user to specify the reference test phase and test type
  returns the users selection.
  If app and spacecraft_name are specified, the test phase will be a drop down combo box rather than a text field'''
  
  ##### New feature:
  #Let the user choose whether to query for same test type and test phase or to
  #use the reference data that is used in the PDF
  #to be implemented in the future
  #####
  
  promptBox = Box(BoxLayout.Y_AXIS)
  promptBox.createVerticalStrut(1)

  boxTestPhase = Box(BoxLayout.X_AXIS)  
  if spacecraft_name == None:
    txtTestPhase = JTextField(default_test_phase, 1)
    lblTestPhase = JLabel('Ref Test Phase: ')
    lblTestPhase.setLabelFor(txtTestPhase)
    objTestPhase = txtTestPhase
  else:
    sep = ' - '
    test_phase_list = get_test_phases_by_spacecraft_name(app, spacecraft_name)
    phase_identifier_list, phase_description_list = zip(*test_phase_list)
    default_index = list(phase_identifier_list).index(default_test_phase)
    #default_string = sep.join(test_phase_list[default_index])
    test_phase_list_strings = [sep.join((phase_identifier, phase_description)) for phase_identifier, phase_description in test_phase_list]
    cboTestPhase = JComboBox(test_phase_list_strings)
    cboTestPhase.setSelectedIndex(default_index)
    lblTestPhase = JLabel('Ref Test Phase: ')
    lblTestPhase.setLabelFor(cboTestPhase)
    objTestPhase = cboTestPhase
  
  boxTestType = Box(BoxLayout.X_AXIS)
  txtTestType = JTextField(str(default_test_type), 4)
  lblTestType = JLabel('Ref Test Type: ')
  lblTestType.setLabelFor(txtTestType)
  
  cbxAbsoluteValue = JCheckBox('Use absolute value of reference delta', False)
  
  # add components to the prompt box
  boxTestPhase.add(lblTestPhase)
  boxTestPhase.add(objTestPhase)
  promptBox.add(boxTestPhase)
  
  boxTestType.add(lblTestType)
  boxTestType.add(txtTestType)
  promptBox.add(boxTestType)
  
  promptBox.add(cbxAbsoluteValue)
  
  title = 'Delta to Reference Option'
  result = JOptionPane.showConfirmDialog(None, promptBox, title, JOptionPane.OK_CANCEL_OPTION)
  
  #extract user inputs from confirm dialog
  reference_plot_display = result == JOptionPane.OK_OPTION
  if spacecraft_name == None:
    reference_phase = txtTestPhase.getText()
  else:
    reference_phase = phase_identifier_list[cboTestPhase.getSelectedIndex()]
  test_type_ref = txtTestType.getText()
  reference_abs_delta = cbxAbsoluteValue.isSelected()
                                                                
  return (reference_plot_display, reference_phase, test_type_ref, reference_abs_delta)
  
def prompt_split_by_field(ssi_fields, redundancy_fields, other_fields, defaultCheckboxState=False):
  title = 'Split by Field Codes'
  message = 'Choose field codes to plot in separate tabs'
  
  
  promptBox = Box.createVerticalBox()
  optionsBox = Box.createHorizontalBox()
  colLeft = Box.createVerticalBox()
  colMid = Box.createVerticalBox()
  colRight = Box.createVerticalBox()
  
  promptBox.add(Box.createVerticalGlue())
  optionsBox.add(Box.createHorizontalGlue())
  colRight.add(Box.createVerticalGlue())
  
  #genericBorder = BorderFactory.createLineBorder(Color.black)
  #optionsBox.setBorder(genericBorder)
  #colLeft.setBorder(genericBorder)
  #colRight.setBorder(genericBorder)
  
  promptBorder = BorderFactory.createTitledBorder(message)
  promptBox.setBorder(promptBorder)
  
  #all_fields = [item for sublist in [ssi_fields, redundancy_fields, other_fields] for item in sublist]
  ssiOptions = []
  redOptions = []
  otherOptions = []
  
  
  if len(ssi_fields) > 0:
    ssiCheckboxGroup = Box(BoxLayout.Y_AXIS)
    
    ssiBorder = BorderFactory.createTitledBorder("SSI Fields")
    ssiCheckboxGroup.setBorder(ssiBorder)
    #ssiCheckbox = JCheckBox('SSI Fields:')
    #ssiCheckbox.setSelected(defaultCheckboxState)
    #promptBox.add(ssiCheckbox)
    for option in ssi_fields:
      checkBox = JCheckBox(option)
      checkBox.setSelected(defaultCheckboxState)
      ssiCheckboxGroup.add(checkBox)
      ssiOptions.append(checkBox)
    colLeft.add(ssiCheckboxGroup)
    
  if len(other_fields) > 0:
    otherCheckboxGroup = Box(BoxLayout.Y_AXIS)
    otherBorder = BorderFactory.createTitledBorder("Other")
    otherCheckboxGroup.setBorder(otherBorder)
    for option in other_fields:
      checkBox = JCheckBox(option)
      checkBox.setSelected(defaultCheckboxState)
      otherCheckboxGroup.add(checkBox)
      otherOptions.append(checkBox)
    colMid.add(otherCheckboxGroup)
  
  if len(redundancy_fields) > 0:
    redCheckboxGroup = Box(BoxLayout.Y_AXIS)
    redBorder = BorderFactory.createTitledBorder("Redundancy")
    redCheckboxGroup.setBorder(redBorder)
    # redCheckbox = JCheckBox('Redundancy Fields:')
    # redCheckbox.setSelected(defaultCheckboxState)
    # promptBox.add(redCheckbox)
    for option in redundancy_fields:
      checkBox = JCheckBox(option)
      checkBox.setSelected(defaultCheckboxState)
      redCheckboxGroup.add(checkBox)
      redOptions.append(checkBox)
    colRight.add(redCheckboxGroup)

  colRight.add(Box.createVerticalGlue())
  optionsBox.add(colLeft)
  optionsBox.add(colMid)
  optionsBox.add(colRight)
  optionsBox.add(Box.createHorizontalGlue())
  
  promptBox.add(optionsBox)
  promptBox.add(Box.createVerticalGlue())
  
  #flatten a list of lists
  #[item for sublist in l for item in sublist]
  
  result = JOptionPane.showConfirmDialog(None, promptBox, title, JOptionPane.OK_CANCEL_OPTION)
  if result == 0: #user didn't press cancel
    ssi_fields_selected        = [option.getText().split(" ",1)[0] for option in ssiOptions   if option.isSelected()] #return only SSI field code, ignore the description appended after the code.
    redundancy_fields_selected = [option.getText() for option in redOptions   if option.isSelected()]
    other_fields_selected      = [option.getText() for option in otherOptions if option.isSelected()]
    return (ssi_fields_selected, redundancy_fields_selected, other_fields_selected)
  else:
    return ([], [], [])


#old. all instances need to be updated
def get_reference_data(app, data, reference_phase, spacecraft_name, reference_test_type=None):
  from collections import namedtuple
  Record = namedtuple('record', 'id spacecraftName payloadSubsystem ssi testType testPhase config runNumber')
  spacecraft_name = app.staarAuthenticatedUserRepository.getSpacecraftByNamePts(data['SSL_Model'], data['SSL_Serial']).name
  record = Record(0, spacecraft_name, data['SSL_SubSystem'], data['SSL_SSI'], data['SSL_TestType'], data['SSL_TestPhase'], data['SSL_ConfigNum'], NaN)
  record_ref = get_reference_record(app, record, reference_phase, reference_test_type)
  return GdfWrapper(app.staarAuthenticatedUserRepository.getParsedGdf(record_ref.id))

def get_reference_record(app, record, test_phase='13', test_type=NaN):
  return get_reference_records(app, record, test_phase, test_type)[0]

def get_reference_records(app, record, test_phase='13', test_type=NaN):
  '''
  Finds the first record that matches the criteria specified in the arguments
  If no records are found, successively relaxes the SSI search criteria
  If still no records are found, raises NoRecordsFoundError
  
  inputs:
  record: PayloadFileRecord
  test_phase: string
  test_type: int, if NaN, uses record.testType
  
  returns: (data_gdf, data_ppd)
  '''
  
  if test_type == NaN:
    test_type = record.testType
  
  #use strict SSI matching
  records = staar_record_query(app, record, test_phase, test_type)
  if not records:
    #relax the SSI matching requirements
    import _printer
    ssi_ignore_fields = (u'AT', u'OP', u'M') #AT=Step Atten, OP=OPA, M=FGM/ALC Mode
    print('\nNo ref match found for %s. Trying loosened SSI match: ignore (%s)' %
          (_printer.PyPayloadFileRecord(record).abridged(), ', '.join(ssi_ignore_fields)))
    records = staar_record_query(app, record, test_phase, test_type, ssi_ignore_fields)
    
    if records:
      print('found relaxed ssi match!')
    else:
      #relax the matching requirements to config number instead of SSI
      print('No ref match found. Trying config match')
      ssi_ignore_fields = (r'.*',) #ignore all ssi fields
      config=record.config
      records = staar_record_query(app, record, test_phase, test_type, ssi_ignore_fields, config)
      
      if records:
        print('found config match!')
      else:
        raise NoRecordsFoundError('unable to find reference for record %s' % _printer.PyPayloadFileRecord(record).abridged())
  return records



def staar_record_query(app, record, test_phase='13', test_type=NaN, ssi_ignore_fields=[], config=None):
  '''This function should be updated to receive a subsystem, config, spacecraft, test type, and test phase to search for
  Alternatively, it could return the ref record in SSL_RefFilename, receiving a record as input
  This function should be updated to return a record or record id.
  Alternatively, it could use _util.get_staar_data to return gdf and ppd data'''
  spacecraft_name = record.spacecraftName
  subsystem = record.payloadSubsystem #u'RPTR' or u'TTC'
  if test_type == NaN:
    test_type = record.testType

  # build ssi query
  partial_ssi = _ssi.partial_ssi(app, spacecraft_name, subsystem, record.ssi, ssi_ignore_fields)
  if partial_ssi:  
    xml_ssi_fragment = ('<criteria not="false"><ssi spacecraftName="%s" subsystem="%s">' % (spacecraft_name, subsystem) 
                        + ''.join(['<field fieldCode="%s" valueCode="%s"/>' 
                        % (code, value) for code, value in partial_ssi]) 
                        + '</ssi></criteria>')
  else:
    xml_ssi_fragment = ''
    
  if config:
    xml_config_fragment = '<criteria not="false"><config regex="%(config)s"/></criteria>' % {'config': config}
  else:
    xml_config_fragment = ''
  
    
  query_values = {'spacecraft_name': spacecraft_name,
                  'test_phase': test_phase,
                  'test_type': str(test_type),
                  'xml_ssi_fragment' : xml_ssi_fragment,
                  'xml_config_fragment' : xml_config_fragment}

  query_template = \
'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<savedQuery xmlns="http://ssd.loral.com/staar/xml/criterion" not="false">
    <complex junction="and">
        <criteria not="false">
            <spacecraft name="%(spacecraft_name)s"/>
        </criteria>
        <criteria not="false">
            <testType spacecraftName="%(spacecraft_name)s" isFiltered="true">
                <id value="%(test_type)s"/>
            </testType>
        </criteria>
        <criteria not="false">
            <testPhase spacecraftName="%(spacecraft_name)s" isFiltered="true">
                <id value="%(test_phase)s"/>
            </testPhase>
        </criteria>
        <criteria not="false">
            <isLatestRun/>
        </criteria>
        %(xml_ssi_fragment)s
        %(xml_config_fragment)s
    </complex>
</savedQuery>'''
  
  my_query_xml = query_template % query_values

  # tell the server to execute the query
  records = app.staarAuthenticatedUserRepository.queryXml(my_query_xml)
  return records

      
class NoRecordsFoundError(Exception):
  pass

  
def get_reference_1190_data(app, test_phase, spacecraft_name, test_type, config_id):
  
  #config_id = data['SSL_ConfigNum']
  #test_type = data['SSL_TestType']
  #test_phase = reference_phase

  # read a saved STAAR+ query into the variable 'my_query_xml'
  #my_path = "C:\Documents and Settings\jains\My Documents"
  #os.chdir(my_path)
  #my_query_xml = open('C:\Documents and Settings\jains\My Documents\test.xml').read()
  #my_query_xml = open("test.xml").read()

  my_query_xml = \
'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<savedQuery xmlns="http://ssd.loral.com/staar/xml/criterion" not="false">
    <complex junction="and">
        <criteria not="false">
            <spacecraft name="%(spacecraft_name)s"/>
        </criteria>
        <criteria not="false">
            <testType spacecraftName="%(spacecraft_name)s" isFiltered="true">
                <id value="%(test_type)s"/>
            </testType>
        </criteria>
        <criteria not="false">
            <testPhase spacecraftName="%(spacecraft_name)s" isFiltered="true">
                <id value="%(test_phase)s"/>
            </testPhase>
        </criteria>
        <criteria not="false">
            <isLatestRun/>
        </criteria>
        <criteria not="false">
            <config regex="%(config_id)s"/>
        </criteria>
    </complex>
</savedQuery>'''

  #my_query_xml = my_query_xml.format(program, test_phase)
  my_query_xml = my_query_xml % {'spacecraft_name': spacecraft_name, 'test_phase': test_phase, 'test_type': test_type, 'config_id': config_id }

  # tell the server to execute the query
  records = app.staarAuthenticatedUserRepository.queryXml(my_query_xml)

  try:
    reference_data = GdfWrapper(app.staarAuthenticatedUserRepository.getParsedGdf(records[0].id))
  except:
    reference_data = {}
    JOptionPane.showMessageDialog(None, "No data found in the specified reference test phase for config " + config_id + "! Press OK to abort.", "ERROR!", JOptionPane.ERROR_MESSAGE)  


  return reference_data

# FIXME: this "assumed" initial state event is duplicated in the Java code
def make_new_blank_state_event(test_result_id):
  '''WILL BE DEPRECATED: StateEvent2 will be replaced by StateChangeRequest'''
  print(make_new_blank_state_event.__docstring__)
  return StateEvent2(0,  # id
                     test_result_id, #test result id
                     '', # timestamp
                     '', # username
                     '', # userAuthenticationDomain
                     '', # userRealName
                     '', #comment
                     False, #comment is proprietary
                     '', #canned comment
                     'new', #SSL Status
                     'new', #Customer status
                     [], #labels
                     []) #ncrs

                     
def dir_tree(var, traversal_depth=1, warn_on_excessive_depth=True):
  if traversal_depth <= 1:
    try:
      return dir(var)
    except Exception, e:
      print(('Terminal child exception', e, var))
      return []
  else:
    if warn_on_excessive_depth and traversal_depth > 0:
      stopExecution = JOptionPane.showConfirmDialog(None,
           'Traversal depth of %i selected. Estimated runtime %i seconds. Continue?' % \
           (traversal_depth, ((len(dir(var))**(traversal_depth+1)-len(dir(var)))*1e-5)))
      if stopExecution == 1 or stopExecution == 2:
        return None
    tree = OrderedDict()
    try:
      for subvar in dir(var):
        try:
          tree[subvar] = dir_tree(var.__getattribute__(subvar), traversal_depth-1, warn_on_excessive_depth=False)
        except Exception, e:
          print(('Child exception', e, subvar))
          continue
    except Exception, e:
      print(('Root exception', e, var))
    return tree

from javax.swing.event import TreeModelListener
from javax.swing.tree import TreeModel, TreePath
class FilteredTreeModel(TreeModel):
  #from http://www.adrianwalker.org/2012/04/filtered-jtree.html
  def __init__(self, treeModel):
    self.treeModel = treeModel
    self.labelFilter = ""
  def recursiveMatch(self, node, labelFilter):
    matches = node.toString().contains(labelFilter)
    childCount = self.treeModel.getChildCount(node)
    for index in xrange(childCount):
      child = self.treeModel.getChild(node, index)
      matches |= self.recursiveMatch(child, labelFilter)
  def getRoot(self): #override
    return self.treeModel.getRoot()
  def getChild(self, parent, child_index): #override
    count = 0
    childCount = self.treeModel.getChildrenCount(parent)
    for index in xrange(childCount): #replace count and child indexing if possible
      child = self.treeModel.getChild(parent, index)
      if self.recursiveMatch(child, self.labelFilter):
        if count==child_index:
          return child
        count += 1
    return None
  def isLeaf(self, node): #override
    return self.treeModel.isLeaf(node)
  def valueForPathChanged(self, path, newValue): #override
    self.treeModel.valueForPathChanged(path, newValue)
  def getIndexOfChild(self, parent, childToFind): #override
    childCount = self.treeModel.getChildCount(parent)
    for index in xrange(childCount):
      child = self.treeModel.getChild(parent, index)
      if self.recursiveMatch(child, self.labelFilter):
        if childToFind.equals(child):
          return index
    raise ChildNotFoundException(parent, childToFind)
  def addTreeModelListener(self, listener): #override
    self.treeModel.addTreeModelListener(listener)
  def removeTreeModelListener(self, listener):
    self.treeModel.removeTreeModelListener(listener)

from java.awt import Container, GridBagConstraints, GridBagLayout
from javax.swing import JFrame, JScrollPane, JTextField, JTree
from javax.swing.event import DocumentEvent, DocumentListener
from javax.swing.tree import DefaultMutableTreeNode, DefaultTreeModel
class FilteredJTreeExample(object):
  def __init__(self):
    self.run()
  def run(self):
    self.createAndShowGUI()
  def createAndShowGUI(self):
    frame = JFrame(title="Filtered JTree Demo",
                   defaultCloseOperation=JFrame.DISPOSE_ON_CLOSE)
    self.addComponentsToPane(frame.getContentPane())
    frame.pack()
    frame.setVisible(True)
  def addComponentsToPane(self, pane):
    pane.setLayout(GridBagLayout())
    tree = self.createTree(pane)
    filterField = self.createFilterField(pane)
    listener = self.createDocumentListener(self, tree, filterField)
    filterField.getDocument().addDocumentListener(listener)
  def createTree(self, pane):
    root = DefaultMutableTreeNode("JTree")
    model = FilteredTreeModel(DefaultTreeModel(root))
    tree = JTree(model)
    scrollPane = JScrollPane(tree)
    constraints = GridBagConstraints(weightx=1,
                                     weighty=1,
                                     fill=GridBagConstraints.BOTH,
                                     gridx=0,
                                     gridy=1);
    pane.add(scrollPane, constraints)
    self.createTreeNodes(root)
    self.expandTree(tree)
    return tree
  
  def createFilterField(self, pane):
    filterField = JTextField()
    constraints = GridBagConstraints(weightx=0,
                                     weighty=0,
                                     fill=GridBagConstraints.HORIZONTAL,
                                     gridx=0,
                                     gridy=0)
    pane.add(filterField, constraints)
    return filterField
  def createDocumentListener(self, filtered_jtree, tree, filterField):
    return FilteredJTreeDocumentListener(filtered_jtree, tree, filterField)
        
  def expandTree(self, tree):
    rowCount = tree.getRowCount()
    for index in xrange(rowCount):
      tree.expandRow(index)
      
  def createTreeNodes(self, node):
    ab = DefaultMutableTreeNode("ab")
    cd = DefaultMutableTreeNode("cd")
    ef = DefaultMutableTreeNode("ef")
    gh = DefaultMutableTreeNode("gh")
    ij = DefaultMutableTreeNode("ij")
    kl = DefaultMutableTreeNode("kl")
    mn = DefaultMutableTreeNode("mn")
    op = DefaultMutableTreeNode("op")
    qr = DefaultMutableTreeNode("qr")
    st = DefaultMutableTreeNode("st")
    uv = DefaultMutableTreeNode("uv")
    wx = DefaultMutableTreeNode("wx")
    yz = DefaultMutableTreeNode("yz")
 
    node.add(ab)
    node.add(cd)
    ab.add(ef)
    ab.add(gh)
    cd.add(ij)
    cd.add(kl)
    ef.add(mn)
    ef.add(op)
    gh.add(qr)
    gh.add(st)
    ij.add(uv)
    ij.add(wx)

    node.add(yz)
    
class FilteredJTreeDocumentListener(DocumentListener):
  def __init__(self, filtered_jtree, tree, filterField):
    self.filtered_jtree = filtered_jtree
    self.tree = tree
    self.filterField = filterField
  def insertUpdate(self, event): #override
    print('insert' + str(event))
    self.applyFilter()
  def removeUpdate(self, event): #override
    self.applyFilter()
  def changedUpdate(self, event): #override
    self.applyFilter()
  def applyFilter(self):
    filteredModel = FilteredTreeModel(self.tree.getModel())
    filteredModel.labelFilter = self.filterField.getText()
#       self.filtered_jtree.treeModel = filteredModel.getTreeModel()
#       self.filtered_jtree.treeModel.reload()
    filteredModel.getTreeModel().reload()
    
    self.filtered_jtree.expandTree(self.tree)
    filteredModel.expandTree(self.tree)
  
  
class ChildNotFoundException(Exception):
  def __init__(self, parent, childToFind):
    self.parent = parent
    self.childToFind = childToFind
  def __str__(self):
    return ("%{child}s child not found at parent %{parent}s" %
            {'child': self.childToFind.toString(),
             'parent': self.parent.toString()})
    
  
def getPPDSpecCorners(data_ppd, package='p1', axis='0'):
  head = 'Spec_'
  x0_min   = head + package + '_x' + axis + '_min'
  x0_max   = head + package + '_x' + axis + '_max'
  x0_major = head + package + '_x' + axis + '_major'
  x0_minor = head + package + '_x' + axis + '_minor'
  y0_min   = head + package + '_y' + axis + '_min'
  y0_max   = head + package + '_y' + axis + '_max'
  y0_major = head + package + '_y' + axis + '_major'
  y0_minor = head + package + '_y' + axis + '_minor'
  
  
def clean_docstring(docstring):
  '''trims of leading whitespace from a docstring
  indented text will remain indented correctly after trimming
  the number of whitespace characters that is trimmed off
  is determined by the number of whitespace characters on the
  second line of the docstring'''
  doclines = docstring.lstrip('\n').rstrip().splitlines()
  whitespace_chars = len(doclines[1]) - len(doclines[1].lstrip())
  cleaned_docstring = doclines[0] + '\n' + '\n'.join([line.rstrip()[whitespace_chars:] for line in doclines[1:]]) 
  return cleaned_docstring


def clear_console(console):
  if console and hasattr(console, 'text_pane'):
    console.text_pane.text = ''

def spur_hound_beta(ul_freqs, dl_freqs, lo_freq):
  hit_freq = []
  i = 0
  for lo_index in range(0,10):
    if lo_freq*lo_index > min(dl_freqs) and lo_freq*lo_index  < max(dl_freqs):
      hit_freq.append(lo_freq*lo_index)
    i = i + 1
  return hit_freq

def smooth_spurs_beta(data,known_spur_freq):
  dummyFreq, dummyGain = zip(*data)
  for temp_index, (temp_freq, temp_gain) in enumerate(data):
    for spur_freq in known_spur_freq:
      if temp_freq <= spur_freq+0.1 and temp_freq >= spur_freq-0.1:
        data[temp_index] = [temp_freq,dummyGain[temp_index-1]]
  return data

def check_UL_DL(freqs):
    if min(freqs) > 10*1e9 and max(freqs) < 13*1e9: #Ku-Band downlink
      check = "downlink"
    elif min(freqs) > 13*1e9 and max(freqs) < 15*1e9: #Ku-Band uplink
      check = "uplink"
    elif min(freqs) > 17*1e9 and max(freqs) <18*1e9: #Ku-Band uplink
      check = "uplink"
    elif min(freqs) > 18*1e9 and max(freqs) <21*1e9: #Ka-Band downlink
      check = "downlink"
    elif min(freqs) > 28*1e9 and max(freqs) <31*1e9: #Ka-Band uplink
      check = "uplink"
    elif min(freqs) > 3*1e9 and max(freqs) <5*1e9: #C-Band downlink
      check = "downlink" 
    elif min(freqs) > 5*1e9 and max(freqs) <7*1e9: #C-Band uplink
      check = "uplink" 
    else:
      dummyOutput = JOptionPane.showMessageDialog(None, "Error in the PNA-X file", "PNA-X Frequency type error", JOptionPane.ERROR_MESSAGE)
    return check 